""" Defines a Vector class.

    WARNING: This module is now deprecated in favor of `sbak.cgeometry`.
    If you use anything from in here, be warned that you will have to
    update your code later on.

    This is the Python version of the vector module. If the
    Cython version of the vector module is not available,
    `sbak.vector` will refer to this module. This module can
    also be accessed directly as `sbak.pvector` regardless of
    whether or not the Cython version is available.
    
    If the Cython version is available, `sbak.vector` will
    refer to that. If available, it can also be accessed
    directly as `sbak.cvector`.
"""

import operator
from math import atan2, hypot, sin, cos, degrees, radians

__all__ = ['Vector']

class Vector(object):
    """ A 2d vector class that supports both x,y axes and polar APIs.
    
        Vector()
        Vector(X, Y)
        Vector((X, Y))
        Vector(x = X, y = Y)
        Vector(deg = DEGREES, mag = MAGNITUDE)
        Vector(rad = RADIANS, mag = MAGNITUDE)
        Vector(magdeg = (MAGNITUDE, DEGREES))
        Vector(magrad = (MAGNITUDE, RADIANS))
    
        The vector class can be used to dynamically switch between two-
        dimensional, (x,y) coodinates and polar (magnitude, angle)
        coordinates. This is done by accessing various properties
        whose values are dynamically generated.
        
        For example, any vector where the x and y coordinate are
        equal (other than x == y == 0) will have an angle of -45
        degrees, or pi/4 radians. This can be seen in the following:
        
        >>> from math import pi
        >>> from sbak.vector import Vector
        >>> v = Vector(10, 10)
        >>> v.deg
        -45.0
        >>> v.rad
        -0.78539816339744828
        >>> -pi/4
        -0.78539816339744828
        
        Assignments can be done to these dynamic properties as well.
        The internal x,y values are updated to reflect the change.
        
        ~~~ A Note about Angle Direction ~~~
        
        In traditional mathematical applications, the Y axis increases
        while moving upwards and decreases while moving downwards.
        However, in SDL and many other game development platforms the
        Y axis is inverted to accomodate the fact that monitors are
        usually refreshed from top to bottom. This means that a lower
        Y value corresponds to a higher position on screen, and a
        higher value means a lower position. For this reason, the
        Vector class calculates angles (both in radian and in degrees)
        with an inverted Y axis so that increasing angles still moves
        the vector counterclockwise when shown inside Y-inverted
        systems.
        
        ~~~ A Note about Zero-Magnitude Vectors ~~~
        
        Note that properties which give angle measurements have one
        caveat that may not be obvious: They retain their value
        whenever the x and y components of the vector are set to 0
        either directly or indirectly. In other words, whenever the
        x and y values of a vector both become 0, the radian and
        degree measures of the angle will each remain unchanged.
        
        This behavior deserves some explanation: In reality, any
        vector with x and y components of eactly 0 will also have a
        magnitude of exactly 0, and this is true of the simulated
        vectors of the Vector class. However, any true vector with
        magnitue 0 points in every direction simultaneously. This
        can't be represented with a single angle measurement value,
        so attempting to compute an angle gives theoretically
        unpredicable results. (In practice the value generated by
        the computer is usually either 0 degrees or 180 degrees, but
        these values are incorrect since no angle value is actually
        possible.)
        
        To solve this problem, the Vector class takes an extra step
        whenever the x and y values are changed to preserve the
        radian measure of the angle if the x and y are both
        changed to 0. This is not mathematically correct behavior,
        but it is much more useful than having an unpredictable
        value for the angle.
    """
    
    __slots__ = ['_x', '_y', '_rad']
    
    def __init__( self,
                  x = None,
                  y = None,
                  xy = None,
                  mag = None,
                  deg = None,
                  rad = None,
                  magdeg = None,
                  magrad = None ):
        
        self._x, self._y = [0.0, 0.0]
        self._rad = 0.0
        
        if x is not None:
            try:
                self._x, self._y = map(float, x[:2])
            except TypeError:
                self.x = x
        if y is not None:
            self.y = y
        
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
        
        if xy is not None:
            self.xy = xy
        if mag is not None:
            self.mag = mag
        if deg is not None:
            self.deg = deg
        if rad is not None:
            self.rad = rad
        if magdeg is not None:
            self.magdeg = magdeg
        if magrad is not None:
            self.magrad = magrad
    
    def __repr__(self):
        return 'Vector(%f, %f)'%self.xy
    
    def set_x(self, val):
        self._x = float(val)
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    x = property(
        fget = lambda self: self._x,
        fset = set_x,
        doc = " The horizontal component as a float. "
    )
    
    def set_y(self, val):
        self._y = float(val)
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    y = property(
        fget = lambda self: self._y,
        fset = set_y,
        doc = " The vertical component as a float."
    )
    
    def set_xy(self, val):
        self._x, self._y = map(float, val)
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    xy = property(
        fget = lambda self: (self._x, self._y),
        fset = set_xy,
        doc = " The horizontal and vertical components as a pair of floats."
    )
    
    def set_rad(self, rad):
        self._rad = rad = float(rad)
        mag = hypot(self._x, self._y)
        self._x, self._y = cos(rad) * mag, -sin(rad) * mag
    rad = property(
        fget = operator.attrgetter('_rad'),
        fset = set_rad,
        doc = " The angle (in radians) as a float. "
    )
    
    def set_deg(self, deg):
        self._rad = rad = radians(deg)
        mag = hypot(self._x, self._y)
        self._x, self._y = cos(rad) * mag, -sin(rad) * mag
    deg = property(
        fget = lambda self: degrees(self._rad),
        fset = set_deg,
        doc = " The angle (in degrees) as a float. "
    )
    
    def set_mag(self, mag):
        self._x, self._y = cos(self._rad) * mag, -sin(self._rad) * mag
    mag = property(
        fget = lambda self: hypot(self._x, self._y),
        fset = set_mag,
        doc = " The magnitude as a float. "
    )
    
    def set_magrad(self, magrad):
        mag, self._rad = magrad
        self._x, self._y = cos(self._rad) * mag, -sin(self._rad) * mag
    magrad = property(
        fget = lambda self: (hypot(self._x, self._y), self._rad),
        fset = set_magrad,
        doc = " Magnitude and angle (in radians) as a pair of floats. "
    )
    
    def set_magdeg(self, magdeg):
        mag, deg = magdeg
        self._rad = radians(deg)
        self._x, self._y = cos(self._rad) * mag, -sin(self._rad) * mag
    magdeg = property(
        fget = lambda self: (hypot(self._x, self._y), degrees(self._rad)),
        fset = set_magdeg,
        doc = " Magnitude and angle (in degrees) as a pair of floats. "
    )
    
    def __iter__(self):
        return iter((self._x, self._y))
    
    def __len__(self):
        return 2
    
    def __getitem__(self, index):
        return (self._x, self._y).__getitem__(index)
    
    def __setitem__(self, index, value):
        """ FIXME: This allows for the [x,y] internal list length to change! """
        xy = [self._x, self._y]
        xy.__setitem__(index, value)
        self._x, self._y = map(float, xy)
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    
    def __add__(self, other):
        return Vector(self.x+other[0], self.y+other[1])
    
    def __iadd__(self, other):
        self._x += other[0]
        self._y += other[1]
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    
    def __sub__(self, other):
        return Vector(self._x-other[0], self._y-other[1])
    
    def __isub__(self, other):
        self._x -= other[0]
        self._y -= other[1]
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    
    def __mul__(self, val):
        return Vector(self.x*val, self.y*val)
    
    def __imul__(self, val):
        self._x *= val
        self._y *= val
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)
    
    def __div__(self, val):
        self._x /= val
        self._y /= val
        if self._x or self._y:
            self._rad = atan2(-self._y, self._x)

    def __eq__(self, other):
        return self.xy == other
    
    def __lt__(self, other):
        return self.xy < other
    
    def __le__(self, other):
        return self.xy <= other
    
    def __gt__(self, other):
        return self.xy > other
    
    def __ge__(self, other):
        return self.xy >= other
    
    def __ne__(self, other):
        return self.xy != other

    def __nonzero__(self):
        return bool(self.mag)
